/// 1. 目的：让单元测试里的函数名与被测试的函数 **同名** 。
/// 2. 插入 `#[bench]` 测试代码，无需手动引入 `test::Bencher` 。
/// 3. 两种测试样式：封装成 `assert_eq!` 型；
///    传入辅助测试函数（里面可测试多个断言）和被测试函数型。
/// 4. 为了避免同时使用两种测试样式造成同名冲突（或者与测试模块下其他函数重名），
/// 5. 可选添加函数名前缀。
/// TODO 为了减少 transcriber 重复
#[macro_export]
macro_rules! bench {
    () => {};

    ($func:ident($($args:expr),*); target = $target:expr $(; prefix = $prefix:ident)?) => {
        paste::paste! {
            #[bench]
            fn [<$($prefix)? $func>](b: &mut test::Bencher) {
                b.iter(|| { assert_eq!($func($($args),*), $target); })
            }
        }
    };
    ($solution:tt:: $func:ident($($args:expr),*); target = $target:expr $(; prefix = $prefix:ident)?) => {
        // ($solution:tt:: $func:ident($($args:expr),*); $(target = )? $target:expr) => {
        paste::paste! {
            #[bench]
            fn [<$($prefix)? $func>](b: &mut test::Bencher) {
                b.iter(|| { assert_eq!($solution::$func($($args),*), $target); })
            }
        }
    };

    ($assert:ident $func:ident $(; prefix = $prefix:ident)?) => {
        paste::paste! {
            #[bench]
            fn [<$($prefix)? $func>](b: &mut test::Bencher) { b.iter(|| { $assert($func); }) }
        }
    };
    ($assert:ident $solution:tt:: $func:ident $(; prefix = $prefix:ident)?) => {
        paste::paste! {
            #[bench]
            fn [<$($prefix)? $func>](b: &mut test::Bencher) { b.iter(|| { $assert($solution::$func); }) }
        }
    };
}

#[cfg(test)]
mod bench {
    fn string_from(s: &str) -> String { String::from(s) }
    bench!(string_from(""); target = ""; prefix = __);
    bench!(String::from(""); target = ""; prefix = __);

    fn assert<F>(f: F)
        where F: Fn(&'static str) -> String {
        assert_eq!(f(""), "");
    }
    bench!(assert string_from; prefix = ___);
    bench!(assert String::from);
}

/// 1. 目的：打印数组和索引名称与位置。
/// 2. 索引名称与位置参数有两种样式：
///    传入外部定义的变量名；传入 `未定义的变量名 = 位置`。
/// 3. 同一个位置多个索引名称时，只打印第一个传入的名称。
/// 4. 索引名称长度超过打印的数组元素长度时，截断名称。
/// 5. 索引位置不必有序。
#[macro_export]
macro_rules! vec_pos {
    () => {};
    ($vec:expr; $($name:ident = $pos:expr),*) => {
        $crate::_macros::vec_pos(format!("{:?}", $vec), vec![$(stringify!($name)),*], vec![$($pos),*])
    };
    ($vec:expr; $($pos:expr),*) => {
        $crate::_macros::vec_pos(format!("{:?}", $vec), vec![$(stringify!($pos)),*], vec![$($pos),*])
    };
}

pub fn vec_pos(v_str: String, names: Vec<&str>, pos: Vec<usize>) -> [String; 3] {
    // 排序、去重
    let mut names_pos = names.into_iter().zip(pos).collect::<Vec<_>>();
    names_pos.sort_by_key(|(_a, b)| *b);
    names_pos.dedup_by_key(|(_a, b)| *b);
    let (names, pos): (Vec<_>, Vec<_>) = names_pos.into_iter().unzip();

    let [mut v_res, mut pos_res, mut name_res]: [String; 3] = Default::default();
    let mut name;
    for (i, v) in v_str.split(',').enumerate() {
        v_res = format!("{}{}", v_res, v);
        if let Ok(p) = pos.binary_search(&i) {
            pos_res = format!("{} {}{}", pos_res, i, " ".repeat(v.len() - i.to_string().len() - 1));
            name = names[p];
            name_res = if v.len() - 1 < name.len() {
                format!("{} {}", name_res, &name[..v.len() - 1])
            } else {
                format!("{} {}{}", name_res, name, " ".repeat(v.len() - name.len() - 1))
            };
        } else {
            pos_res = format!("{}{}", pos_res, " ".repeat(v.len()),);
            name_res = format!("{}{}", name_res, " ".repeat(v.len()));
        }
    }

    println!("{}\n{}\n{}", v_res, pos_res, name_res);
    [v_res, pos_res, name_res]
}

#[test]
fn pos() {
    let v = vec![1, 2, 3];
    assert_eq!(vec_pos!(v; i = 1, j = 2, k = 0), ["[1 2 3]", " 0 1 2 ", " k i j "]);
    assert_eq!(vec_pos!(v; i = 1, j = 1, k = 0), ["[1 2 3]", " 0 1   ", " k i   "]);
    let n = 1;
    assert_eq!(vec_pos!(v; n), ["[1 2 3]", "   1   ", "   n   "]);
    assert_eq!(vec_pos!(v; i = n, j = 1, k = 0), ["[1 2 3]", " 0 1   ", " k i   "]);

    let v = vec!["hello", "world", "!"];
    assert_eq!(vec_pos!(v; i = 1, j_trunc = 2, k = 0), ["[\"hello\" \"world\" \"!\"]",
                                                        " 0       1       2   ",
                                                        " k       i       j_tr"]);

    let v = vec![1.2, 0., -8.5];
    assert_eq!(vec_pos!(v; i = 1, j = 2, k = 0), ["[1.2 0.0 -8.5]",
                                                  " 0   1   2    ",
                                                  " k   i   j    "]);
}

/// TODO
#[macro_export]
macro_rules! assertions {
    () => {};
    ( $solution:tt:: $func:ident; $(($($input:expr),*; $target:expr)),* $(,)? ) => {
        $(assert_eq!($solution::$func($($input),*), $target);)*
    };
}
